package net.csiit.platform.codeguard.util;

import net.csiit.platform.codeguard.spring.archive.Archive;
import net.csiit.platform.codeguard.spring.archive.JarFileArchive;

import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.jar.Manifest;

/**
 * @author adam
 * @date 2020/7/2
 */
public class ArchiveUtil {
    public static final String SPRING_BOOT_VERSION = "Spring-Boot-Version";
    public static final String SPRING_BOOT_CLASSES = "Spring-Boot-Classes";
    public static final String SPRING_BOOT_LIB = "Spring-Boot-Lib";

    /**
     * include filter for search in spring boot fat jar
     */
    public static final Archive.EntryFilter SPRING_BOOT_INCLUDE_FILTER = (entry) -> {
        if (entry.isDirectory()) {
            return entry.getName().equals("BOOT-INF/classes/");
        }
        return entry.getName().startsWith("BOOT-INF/lib/");
    };

    /**
     * search filter for spring boot fat jar
     */
    public static final Archive.EntryFilter SPRING_BOOT_SEARCH_FILTER = (entry) -> {
        return entry.getName().startsWith("BOOT-INF/");
    };

    /**
     * check if the manifest represents a Spring Boot fat jar
     * @param manifest jar manifest
     * @return
     */
    public static boolean isSpringBootFatJar(Manifest manifest) {
        if (manifest == null) {
            return false;
        }
        String version = manifest.getMainAttributes().getValue(SPRING_BOOT_VERSION);
        String classes = manifest.getMainAttributes().getValue(SPRING_BOOT_CLASSES);
        String lib = manifest.getMainAttributes().getValue(SPRING_BOOT_LIB);

        return !StringUtils.isEmpty(version)
                && !StringUtils.isEmpty(classes)
                && !StringUtils.isEmpty(lib);
    }

    /**
     * get all classpath urls in jar file archive
     * @param archive root
     * @return
     * @throws Exception
     */
    public static List<URL> getClassPathUrls(JarFileArchive archive) throws Exception {
        Manifest manifest = archive.getManifest();
        // if the jar is spring boot fat jar
        if (isSpringBootFatJar(manifest)) {
            return getClassPathUrls(archive, SPRING_BOOT_SEARCH_FILTER, SPRING_BOOT_INCLUDE_FILTER);
        } else {
            return Collections.singletonList(archive.getUrl());
        }
    }

    /**
     * get all classpath urls in archive
     * @param archive root
     * @param searchFilter search filter
     * @param includeFilter include filter
     * @return
     * @throws Exception
     */
    public static List<URL> getClassPathUrls(Archive archive, Archive.EntryFilter searchFilter, Archive.EntryFilter includeFilter) throws Exception {
        if (archive == null) {
            return Collections.emptyList();
        }

        List<URL> urls = new ArrayList<>(50);
        traverseArchive(archive, searchFilter, includeFilter, urls);
        return urls;
    }

    /**
     * travels archive to find all urls
     * @param archive
     * @param searchFilter
     * @param includeFilter
     * @param urls
     * @throws Exception
     */
    public static void traverseArchive(Archive archive, Archive.EntryFilter searchFilter, Archive.EntryFilter includeFilter, List<URL> urls) throws Exception {
        urls.add(archive.getUrl());
        Iterator<Archive> iterator = archive.getNestedArchives(searchFilter, includeFilter);
        while (iterator.hasNext()) {
            Archive arc = iterator.next();
            traverseArchive(arc, searchFilter, includeFilter, urls);
            arc.close();
        }
    }



}
